// Copyright 2013 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_BASE_PLATFORM_MUTEX_H_
#define V8_BASE_PLATFORM_MUTEX_H_

#include "src/base/base-export.h"
#include "src/base/lazy-instance.h"
#if V8_OS_WIN
#include "src/base/win32-headers.h"
#endif
#include "src/base/logging.h"

#if V8_OS_POSIX
#include <pthread.h> // NOLINT
#endif

namespace v8 {
namespace base {

    // ----------------------------------------------------------------------------
    // Mutex - a replacement for std::mutex
    //
    // This class is a synchronization primitive that can be used to protect shared
    // data from being simultaneously accessed by multiple threads. A mutex offers
    // exclusive, non-recursive ownership semantics:
    // - A calling thread owns a mutex from the time that it successfully calls
    //   either |Lock()| or |TryLock()| until it calls |Unlock()|.
    // - When a thread owns a mutex, all other threads will block (for calls to
    //   |Lock()|) or receive a |false| return value (for |TryLock()|) if they
    //   attempt to claim ownership of the mutex.
    // A calling thread must not own the mutex prior to calling |Lock()| or
    // |TryLock()|. The behavior of a program is undefined if a mutex is destroyed
    // while still owned by some thread. The Mutex class is non-copyable.

    class V8_BASE_EXPORT Mutex final {
    public:
        Mutex();
        ~Mutex();

        // Locks the given mutex. If the mutex is currently unlocked, it becomes
        // locked and owned by the calling thread, and immediately. If the mutex
        // is already locked by another thread, suspends the calling thread until
        // the mutex is unlocked.
        void Lock();

        // Unlocks the given mutex. The mutex is assumed to be locked and owned by
        // the calling thread on entrance.
        void Unlock();

        // Tries to lock the given mutex. Returns whether the mutex was
        // successfully locked.
        bool TryLock() V8_WARN_UNUSED_RESULT;

        // The implementation-defined native handle type.
#if V8_OS_POSIX
        using NativeHandle = pthread_mutex_t;
#elif V8_OS_WIN
        using NativeHandle = SRWLOCK;
#endif

        NativeHandle& native_handle()
        {
            return native_handle_;
        }
        const NativeHandle& native_handle() const
        {
            return native_handle_;
        }

    private:
        NativeHandle native_handle_;
#ifdef DEBUG
        int level_;
#endif

        V8_INLINE void AssertHeldAndUnmark()
        {
#ifdef DEBUG
            DCHECK_EQ(1, level_);
            level_--;
#endif
        }

        V8_INLINE void AssertUnheldAndMark()
        {
#ifdef DEBUG
            DCHECK_EQ(0, level_);
            level_++;
#endif
        }

        friend class ConditionVariable;

        DISALLOW_COPY_AND_ASSIGN(Mutex);
    };

    // POD Mutex initialized lazily (i.e. the first time Pointer() is called).
    // Usage:
    //   static LazyMutex my_mutex = LAZY_MUTEX_INITIALIZER;
    //
    //   void my_function() {
    //     MutexGuard guard(my_mutex.Pointer());
    //     // Do something.
    //   }
    //
    using LazyMutex = LazyStaticInstance<Mutex, DefaultConstructTrait<Mutex>,
        ThreadSafeInitOnceTrait>::type;

#define LAZY_MUTEX_INITIALIZER LAZY_STATIC_INSTANCE_INITIALIZER

    // -----------------------------------------------------------------------------
    // RecursiveMutex - a replacement for std::recursive_mutex
    //
    // This class is a synchronization primitive that can be used to protect shared
    // data from being simultaneously accessed by multiple threads. A recursive
    // mutex offers exclusive, recursive ownership semantics:
    // - A calling thread owns a recursive mutex for a period of time that starts
    //   when it successfully calls either |Lock()| or |TryLock()|. During this
    //   period, the thread may make additional calls to |Lock()| or |TryLock()|.
    //   The period of ownership ends when the thread makes a matching number of
    //   calls to |Unlock()|.
    // - When a thread owns a recursive mutex, all other threads will block (for
    //   calls to |Lock()|) or receive a |false| return value (for |TryLock()|) if
    //   they attempt to claim ownership of the recursive mutex.
    // - The maximum number of times that a recursive mutex may be locked is
    //   unspecified, but after that number is reached, calls to |Lock()| will
    //   probably abort the process and calls to |TryLock()| return false.
    // The behavior of a program is undefined if a recursive mutex is destroyed
    // while still owned by some thread. The RecursiveMutex class is non-copyable.

    class V8_BASE_EXPORT RecursiveMutex final {
    public:
        RecursiveMutex();
        ~RecursiveMutex();

        // Locks the mutex. If another thread has already locked the mutex, a call to
        // |Lock()| will block execution until the lock is acquired. A thread may call
        // |Lock()| on a recursive mutex repeatedly. Ownership will only be released
        // after the thread makes a matching number of calls to |Unlock()|.
        // The behavior is undefined if the mutex is not unlocked before being
        // destroyed, i.e. some thread still owns it.
        void Lock();

        // Unlocks the mutex if its level of ownership is 1 (there was exactly one
        // more call to |Lock()| than there were calls to unlock() made by this
        // thread), reduces the level of ownership by 1 otherwise. The mutex must be
        // locked by the current thread of execution, otherwise, the behavior is
        // undefined.
        void Unlock();

        // Tries to lock the given mutex. Returns whether the mutex was
        // successfully locked.
        bool TryLock() V8_WARN_UNUSED_RESULT;

    private:
        // The implementation-defined native handle type.
#if V8_OS_POSIX
        using NativeHandle = pthread_mutex_t;
#elif V8_OS_WIN
        using NativeHandle = CRITICAL_SECTION;
#endif

        NativeHandle native_handle_;
#ifdef DEBUG
        int level_;
#endif

        DISALLOW_COPY_AND_ASSIGN(RecursiveMutex);
    };

    // POD RecursiveMutex initialized lazily (i.e. the first time Pointer() is
    // called).
    // Usage:
    //   static LazyRecursiveMutex my_mutex = LAZY_RECURSIVE_MUTEX_INITIALIZER;
    //
    //   void my_function() {
    //     LockGuard<RecursiveMutex> guard(my_mutex.Pointer());
    //     // Do something.
    //   }
    //
    using LazyRecursiveMutex = LazyStaticInstance<RecursiveMutex, DefaultConstructTrait<RecursiveMutex>,
        ThreadSafeInitOnceTrait>::type;

#define LAZY_RECURSIVE_MUTEX_INITIALIZER LAZY_STATIC_INSTANCE_INITIALIZER

    // ----------------------------------------------------------------------------
    // SharedMutex - a replacement for std::shared_mutex
    //
    // This class is a synchronization primitive that can be used to protect shared
    // data from being simultaneously accessed by multiple threads. In contrast to
    // other mutex types which facilitate exclusive access, a shared_mutex has two
    // levels of access:
    // - shared: several threads can share ownership of the same mutex.
    // - exclusive: only one thread can own the mutex.
    // Shared mutexes are usually used in situations when multiple readers can
    // access the same resource at the same time without causing data races, but
    // only one writer can do so.
    // The SharedMutex class is non-copyable.

    class V8_BASE_EXPORT SharedMutex final {
    public:
        SharedMutex();
        ~SharedMutex();

        // Acquires shared ownership of the {SharedMutex}. If another thread is
        // holding the mutex in exclusive ownership, a call to {LockShared()} will
        // block execution until shared ownership can be acquired.
        // If {LockShared()} is called by a thread that already owns the mutex in any
        // mode (exclusive or shared), the behavior is undefined.
        void LockShared();

        // Locks the SharedMutex. If another thread has already locked the mutex, a
        // call to {LockExclusive()} will block execution until the lock is acquired.
        // If {LockExclusive()} is called by a thread that already owns the mutex in
        // any mode (shared or exclusive), the behavior is undefined.
        void LockExclusive();

        // Releases the {SharedMutex} from shared ownership by the calling thread.
        // The mutex must be locked by the current thread of execution in shared mode,
        // otherwise, the behavior is undefined.
        void UnlockShared();

        // Unlocks the {SharedMutex}. It must be locked by the current thread of
        // execution, otherwise, the behavior is undefined.
        void UnlockExclusive();

        // Tries to lock the {SharedMutex} in shared mode. Returns immediately. On
        // successful lock acquisition returns true, otherwise returns false.
        // This function is allowed to fail spuriously and return false even if the
        // mutex is not currenly exclusively locked by any other thread.
        bool TryLockShared() V8_WARN_UNUSED_RESULT;

        // Tries to lock the {SharedMutex}. Returns immediately. On successful lock
        // acquisition returns true, otherwise returns false.
        // This function is allowed to fail spuriously and return false even if the
        // mutex is not currently locked by any other thread.
        // If try_lock is called by a thread that already owns the mutex in any mode
        // (shared or exclusive), the behavior is undefined.
        bool TryLockExclusive() V8_WARN_UNUSED_RESULT;

    private:
        // The implementation-defined native handle type.
#if V8_OS_POSIX
        using NativeHandle = pthread_rwlock_t;
#elif V8_OS_WIN
        using NativeHandle = SRWLOCK;
#endif

        NativeHandle native_handle_;

        DISALLOW_COPY_AND_ASSIGN(SharedMutex);
    };

    // -----------------------------------------------------------------------------
    // LockGuard
    //
    // This class is a mutex wrapper that provides a convenient RAII-style mechanism
    // for owning a mutex for the duration of a scoped block.
    // When a LockGuard object is created, it attempts to take ownership of the
    // mutex it is given. When control leaves the scope in which the LockGuard
    // object was created, the LockGuard is destructed and the mutex is released.
    // The LockGuard class is non-copyable.

    // Controls whether a LockGuard always requires a valid Mutex or will just
    // ignore it if it's nullptr.
    enum class NullBehavior { kRequireNotNull,
        kIgnoreIfNull };

    template <typename Mutex, NullBehavior Behavior = NullBehavior::kRequireNotNull>
    class LockGuard final {
    public:
        explicit LockGuard(Mutex* mutex)
            : mutex_(mutex)
        {
            if (has_mutex())
                mutex_->Lock();
        }
        ~LockGuard()
        {
            if (has_mutex())
                mutex_->Unlock();
        }

    private:
        Mutex* const mutex_;

        bool V8_INLINE has_mutex() const
        {
            DCHECK_IMPLIES(Behavior == NullBehavior::kRequireNotNull,
                mutex_ != nullptr);
            return Behavior == NullBehavior::kRequireNotNull || mutex_ != nullptr;
        }

        DISALLOW_COPY_AND_ASSIGN(LockGuard);
    };

    using MutexGuard = LockGuard<Mutex>;

    enum MutexSharedType : bool { kShared = true,
        kExclusive = false };

    template <MutexSharedType kIsShared,
        NullBehavior Behavior = NullBehavior::kRequireNotNull>
    class SharedMutexGuard final {
    public:
        explicit SharedMutexGuard(SharedMutex* mutex)
            : mutex_(mutex)
        {
            if (!has_mutex())
                return;
            if (kIsShared) {
                mutex_->LockShared();
            } else {
                mutex_->LockExclusive();
            }
        }
        ~SharedMutexGuard()
        {
            if (!has_mutex())
                return;
            if (kIsShared) {
                mutex_->UnlockShared();
            } else {
                mutex_->UnlockExclusive();
            }
        }

    private:
        SharedMutex* const mutex_;

        bool V8_INLINE has_mutex() const
        {
            DCHECK_IMPLIES(Behavior == NullBehavior::kRequireNotNull,
                mutex_ != nullptr);
            return Behavior == NullBehavior::kRequireNotNull || mutex_ != nullptr;
        }

        DISALLOW_COPY_AND_ASSIGN(SharedMutexGuard);
    };

} // namespace base
} // namespace v8

#endif // V8_BASE_PLATFORM_MUTEX_H_
